/**
 * 
 */
package daytrader.framework;

import java.lang.Math;
import java.io.*;
import java.util.*;

import daytrader.common.Constants;
import daytrader.lib.ExchangeTime;
import daytrader.strategy.*;
import daytrader.types.*;
import daytrader.lib.LibData;
import daytrader.lib.LibDataInterface;
import daytrader.lib.Indicators;

/**
 * @author jay
 * 
 */
public class Engine implements EngineStrategyInterface {
	private int lastTick = 0;
	private Portfolio portfolio = new Portfolio();
	private Hashtable<String, String> configSettings = new Hashtable<String, String>();
	private Account stockAccount = new Account();
	private Stock current;
	private String userConfigFileName;
	private LibDataInterface libData;
	private BuyStrategy buy;
	private SellStrategy sell;
	private List<String> observingStockNames;
	private RepaintEventInterface repaintNotifier;

	public Engine(String userConfigFileName, String dataPath, String observeListFileName) {
		this.userConfigFileName = userConfigFileName;
		this.libData = new LibData(dataPath);
		this.buy = new BuyStrategy(this);
		this.sell = new SellStrategy(this);
		UserDefine.ind = new Indicators(this.libData);
		UserDefine.engine = this;
		this.observingStockNames = initObservingStocks(observeListFileName);
	}
	public void registerRepaintNotifier(RepaintEventInterface e) {
	        this.repaintNotifier = e;
	}

	public void run() {
		System.out.println("hello daytrader");
		init();
		mainLoop();
	}
	
	private void init() {
		readConfig();
		initAccount();
	}
		
	// Initialize user account information
	private void initAccount() {
		try {
			this.stockAccount.account_number =
				this.configSettings.get(Constants.KEY_ACCOUNT_NUMBER);
			this.stockAccount.owner_name =
				this.configSettings.get(Constants.KEY_ACCOUNT_OWNER);
			this.stockAccount.balance = Float.parseFloat(
				this.configSettings.get(Constants.KEY_ACCOUNT_BLANCE));
		} catch (Exception ex) {
			System.out.println("Error happened in reading the account information.");
			System.out.println("Check your config file for the account section.");
			System.exit(-1);
		}
	}

	private void readConfig(){
		 try {
			 FileReader input = new FileReader(userConfigFileName);
			 BufferedReader bufRead = new BufferedReader(input);
			 String line = bufRead.readLine();       
		         
			// Read the config file
			 while (line != null){
				if (line.startsWith("#") || line == "") {
					line = bufRead.readLine();
					continue;
				}
				String[] tokens = line.split("[=\\s]+");
				line = bufRead.readLine();
				if (tokens.length < 2) continue;
				this.configSettings.put(tokens[0], tokens[1]);		
			} 
			bufRead.close();

			Enumeration<String> e = this.configSettings.keys();
			while(e.hasMoreElements()) {
				String key = e.nextElement();
				System.out.println("\"" + key + "\", " + "\"" + configSettings.get(key) + "\"");
			}
			
		} catch (IOException e) {
			// If exception is generated, print a stack trace
			e.printStackTrace(); 
		}
	}

	private void mainLoop() {
		while (true) {
			// the main try/catch block
			try {
				// the buy loop, which will exit whenever
				// any stock/option is bought
				this.buyLoop();

				// the sell loop, which will exit until all
				// the stocks/options bought above are sold
				this.sellLoop();

			} catch (Exception e) {
				// Stop the engine if any unexpected error
				// happens
				e.printStackTrace();
				return;
			}

		}
	}

	private void buyLoop() {
		System.out.println("start buy loop");
		BuyStrategy buy = this.buy;

		while (true) {
			// return immediately if the market is about
			// to close
			if (ExchangeTime.IsAboutToClose())
				break;

			// wait for a new tick
			this.waitNextTick();

			// notify to update UI
			if (this.repaintNotifier != null)
			   repaintNotifier.repaint();

			// run buy strategy,
			buy.run();
			//System.out.println("+++++ " + this.portfolio.stocks.get(0).stock + " " + this.portfolio.stocks.get(0).amount);
			
			// break the loop if the portfolio is not empty
			if (!this.portfolio.IsEmpty()) 
				break;
		}
	}

	private void sellLoop() {
		System.out.println("start sell loop");
		SellStrategy sell = this.sell;

		while (true) {
			// wait for a new tick
			this.waitNextTick();

			// notify to update UI
			if (this.repaintNotifier != null)
			   repaintNotifier.repaint();

			// if the market is about to close, run the
			// loss-cutting routine to limit risk
			if (ExchangeTime.IsAboutToClose()) {
				sell.cut();
			} else {
				sell.run();
			}
            //System.out.println("----- " + this.portfolio.stocks.get(0).stock + " " + this.portfolio.stocks.get(0).amount);

			// break loop only if all the holds are sold
			if (this.portfolio.IsEmpty()) break;
		}
	}

	private void waitNextTick() {
		this.lastTick = Tick.waitNextTick(this.lastTick);
		System.out.println("tick: " + this.lastTick);
	}
	public void setCurrent(Stock current) {
		this.current = current;
		libData.openStockFeed(current);
	}
	public void setCurrent(Hold current) {
	        Stock stock = current.stock;
	        stock.hid = current.id;
		this.current = stock;
		libData.openStockFeed(stock);
	}
	public Stock getCurrent() {
		return current;
	}
	public LibDataInterface getLibData() {
		return libData;
	}
	public Portfolio getPortfolio() {
		return portfolio;
	}
	public Hold getCurrentHold() {
	    Hold ch = null;
	    // find the current hold by checking ids.
	    for (Hold hold: portfolio.stocks) {
                if (hold.id == current.hid) {
                   ch = hold;
                   break;
                }
            }
            return ch;
        }
	/*
	 * Get profit of current Hold.
	 */
	public double getProfit() {
	   Hold ch = getCurrentHold();
           if (ch==null) {
               System.err.println("[DayTrayder::getProfit] No such current Hold.");
               return 0;
           }
           double boughtPrice = ch.price;
	   double currClosePrice = libData.close();
	   double profit = (currClosePrice - boughtPrice) / boughtPrice;
	   if (profit < 0)
	      profit = 0.0;
	   return profit;
        }
	/*
	 * Get loss of current Hold.
	 */
	public double getLoss() {
	   Hold ch = getCurrentHold();
           if (ch==null) {
               System.err.println("[DayTrayder::getLoss] No such current Hold.");
               return 0;
           }
           double boughtPrice = ch.price;
	   double currClosePrice = libData.close();
	   double loss = (boughtPrice - currClosePrice) / boughtPrice;
	   if (loss < 0)
	      loss = 0.0;
	   return loss;
        }
	/*
	 * Get rise of current Stock.
	 */
	public double getRise() {
	   double dayOpenPrice = libData.dayopen();
	   double currClosePrice = libData.close();
	   double rise = (currClosePrice-dayOpenPrice)/dayOpenPrice;
	   if (rise < 0)
	      rise = 0.0;
	   return rise;
        }
	/*
	 * Get fall of current Stock.
	 */
	public double getFall() {
	   double dayOpenPrice = libData.dayopen();
	   double currClosePrice = libData.close();
	   double fall = (dayOpenPrice-currClosePrice)/dayOpenPrice;
	   if (fall < 0)
	      fall = 0.0;
	   return fall;
        }
	/*
	 * Buy the current stock.
	 */
	public void buy(int unit) {
		if (unit < 0) {
                    System.err.println("[DayTrayder] Warning: buying negative units.");
		    return;
	        }
		double currClosePrice = libData.close();
		double cost = (double) unit * currClosePrice;
		if (this.stockAccount.balance >= cost) {
			this.stockAccount.balance -= cost;
			Hold hold = new Hold(current, unit, currClosePrice);
			portfolio.stocks.add(hold);
			System.out.println("[Buy] " + unit + " units of "
					   + current.company_name()
					   + " bought. Balance = " + this.stockAccount.balance);
		} else {
			System.out.println("[Buy] No sufficient funds to buy "
					   + unit + " units of "
					   + current.company_name() + ".");
		}
	}
	/*
	 * Sell the current stock.
	 */
	public void sell(int unit) {
	    if (unit < 0) {
                  System.err.println("[DayTrayder] Warning: selling negative units.");
		  return;
	    }
	    Hold ch = getCurrentHold();
            if (ch==null) {
               System.err.println("[DayTrayder::sell] No such current Hold.");
               return;
            }
            dosell(ch, unit);
	}
	private void dosell(Hold ch, int unit) {
	    unit = Math.min(ch.amount, unit);
	    double currClosePrice = libData.close();
	    double money = (double) unit * currClosePrice;
	    this.stockAccount.balance += money;
	    ch.amount -= unit;
	    if (ch.amount == 0) {
                int index = portfolio.stocks.indexOf(ch);
                portfolio.stocks.remove(index);
            }
            System.out.println("[Sell] " + unit + " units of "
                               + current.company_name()
                               + " sold. Balance = " + this.stockAccount.balance + ", "
                               + "Asset =" + this.getAsset());
        }
	public double getAsset() {
	   double marketValue = 0.0;
	   for (Hold h: portfolio.stocks) {
	      double currClosePrice = libData.close();
	      marketValue += currClosePrice * h.amount;
           }
           return this.stockAccount.balance + marketValue;
	}
	public List<String> getObservingStockNameList() {
	   return this.observingStockNames;
        }
        private List<String> initObservingStocks(String observeListFileName) {
	   ArrayList<String> stockNames = new ArrayList<String>();
	   try {
		FileInputStream fis = new FileInputStream(observeListFileName); 
		BufferedReader in = new BufferedReader(new InputStreamReader(fis, "UTF-8"));
		String stockName;
		while (true) {
			stockName = in.readLine();
			if (stockName != null && stockName.length() > 0) {
			   stockNames.add(stockName);
			} else {
			   break;
                        }
		}
	   } catch (IOException e) {
	       System.err.println("[DayTrader] engine failed to read observelist.");
	   }
           return stockNames;
	}
}
